/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.googlecode.logVisualizer.logData.logSummary;

import java.util.Collection;

import com.googlecode.logVisualizer.logData.Item;
import com.googlecode.logVisualizer.logData.turn.TurnInterval;

/**
 * This immutable class calculates and holds all the data on various quest
 * turncounts.
 */
public final class QuestTurncounts {
    private final int mosquitoQuestTurns;

    private final int templeOpeningTurns;

    private final int tavernQuestTurns;

    private final int batQuestTurns;

    private final int knobQuestTurns;

    private final int friarsQuestTurns;

    private final int cyrptQuestTurns;

    private final int trapzorQuestTurns;

    private final int chasmQuestTurns;

    private final int airshipQuestTurns;

    private final int castleQuestTurns;

    private final int ballroomOpeningTurns;

    private final int pirateQuestTurns;

    private final int blackForrestQuestTurns;

    private final int desertOasisQuestTurns;

    private final int spookyravenQuestTurns;

    private final int templeCityQuestTurns;

    private final int palindomeQuestTurns;

    private final int pyramidQuestTurns;

    private final int warIslandOpeningTurns;

    private final int warIslandQuestTurns;

    private final int dodQuestTurns;

    /**
     * Constructs a new instance and calculates all quest turncounts.
     * 
     * @param turns
     *            The turn rundown of the ascension.
     * @param droppedItems
     *            All dropped items during the ascension.
     */
    public QuestTurncounts(
                           final Collection<TurnInterval> turns, final Collection<Item> droppedItems) {
        if (turns == null)
            throw new NullPointerException("Turn rundown set must not be null.");
        if (droppedItems == null)
            throw new NullPointerException("Dropped items list must not be null.");

        mosquitoQuestTurns = getTurnsUntilItemFound("Spooky Forest",
                                                    "mosquito larva",
                                                    turns,
                                                    droppedItems);

        templeOpeningTurns = calculateTempleOpening(turns, droppedItems);

        tavernQuestTurns = getTurnsInLocation("Typical Tavern Quest", turns);

        batQuestTurns = getTurnsUntilOtherLocationOpen("Guano Junction", "Boss Bat's Lair", turns)
                        + getTurnsInLocation("Boss Bat's Lair", turns);

        knobQuestTurns = getTurnsUntilItemFound("Outskirts of The Knob",
                                                "Knob Goblin encryption key",
                                                turns,
                                                droppedItems)
                         + getTurnsInLocation("Knob Goblin Harem", turns)
                         + getTurnsInLocation("King's Chamber", turns);

        friarsQuestTurns = getTurnsUntilItemFound("Dark Neck of the Woods",
                                                  "dodecagram",
                                                  turns,
                                                  droppedItems)
                           + getTurnsUntilItemFound("Dark Heart of the Woods",
                                                    "box of birthday candles",
                                                    turns,
                                                    droppedItems)
                           + getTurnsUntilItemFound("Dark Elbow of the Woods",
                                                    "eldritch butterknife",
                                                    turns,
                                                    droppedItems);

        cyrptQuestTurns = getTurnsInLocation("Defiled Cranny", turns)
                          + getTurnsInLocation("Defiled Nook", turns)
                          + getTurnsInLocation("Defiled Alcove", turns)
                          + getTurnsInLocation("Defiled Niche", turns)
                          + getTurnsInLocation("Haert of the Cyrpt", turns);

        trapzorQuestTurns = getTurnsInLocation("Itznotyerzitz Mine", turns)
                            + getTurnsInLocation("Mining (In Disguise)", turns)
                            + getTurnsInLocation("Goatlet", turns);

        chasmQuestTurns = getTurnsInLocation("Orc Chasm", turns);

        airshipQuestTurns = getTurnsUntilItemFound("Fantasy Airship",
                                                   "S.O.C.K.",
                                                   turns,
                                                   droppedItems);

        castleQuestTurns = getTurnsUntilOtherLocationOpen("Giant's Castle",
                                                          "Hole in the Sky",
                                                          turns);

        ballroomOpeningTurns = getTurnsUntilOtherLocationOpen("Haunted Pantry",
                                                              "Haunted Billiards Room",
                                                              turns)
                               + getTurnsUntilOtherLocationOpen("Haunted Billiards Room",
                                                                "Haunted Library",
                                                                turns)
                               + getTurnsUntilOtherLocationOpen("Haunted Library",
                                                                "Haunted Bedroom",
                                                                turns)
                               + getTurnsUntilOtherLocationOpen("Haunted Bedroom",
                                                                "Haunted Ballroom",
                                                                turns);

        pirateQuestTurns = getTurnsUntilOtherLocationOpen("Pirate Cove", "Barrrney's Barrr", turns)
                           + getTurnsUntilOtherLocationOpen("Barrrney's Barrr", "F'c'le", turns)
                           + getTurnsInLocation("Cap'm Caronch's Map", turns)
                           + getTurnsInLocation("The Infiltrationist", turns)
                           + getTurnsUntilOtherLocationOpen("F'c'le", "Poop Deck", turns)
                           + getTurnsUntilOtherLocationOpen("Poop Deck", "Belowdecks", turns)
                           + getTurnsUntilOtherLocationOpen("Belowdecks", "Palindome", turns);

        blackForrestQuestTurns = getTurnsInLocation("Black Forest", turns);

        desertOasisQuestTurns = getTurnsInLocation("Desert (Unhydrated)", turns)
                                + getTurnsInLocation("Desert (Ultrahydrated)", turns)
                                + getTurnsInLocation("Oasis in the Desert", turns);

        spookyravenQuestTurns = getTurnsAfterLocationOpen("Haunted Ballroom", "Black Forest", turns)
                                + getTurnsInLocation("Haunted Wine Cellar (Northwest)", turns)
                                + getTurnsInLocation("Haunted Wine Cellar (Northeast)", turns)
                                + getTurnsInLocation("Haunted Wine Cellar (Southwest)", turns)
                                + getTurnsInLocation("Haunted Wine Cellar (Southeast)", turns)
                                + getTurnsInLocation("Haunted Wine Cellar", turns)
                                + getTurnsInLocation("Lord Spookyraven", turns)
                                + getTurnsInLocation("Summoning Chamber", turns);

        templeCityQuestTurns = getTurnsInLocation("Hidden Temple", turns)
                               + getTurnsInLocation("Hidden City", turns)
                               + getTurnsInLocation("Hidden City (Altar)", turns)
                               + getTurnsInLocation("Hidden City (Temple)", turns);

        palindomeQuestTurns = getTurnsInLocation("Palindome", turns)
                              + getTurnsAfterLocationOpen("Knob Goblin Laboratory",
                                                          "Palindome",
                                                          turns)
                              + getTurnsAfterLocationOpen("Whitey's Grove", "Palindome", turns);

        pyramidQuestTurns = getTurnsInLocation("The Upper Chamber", turns)
                            + getTurnsInLocation("The Middle Chamber", turns)
                            + getTurnsInLocation("The Lower Chamber", turns)
                            + getTurnsInLocation("The Lower Chambers (Token/Empty)", turns)
                            + getTurnsInLocation("The Lower Chambers (Rubble/Bomb)", turns)
                            + getTurnsInLocation("The Lower Chambers (Empty/Empty/Ed's Chamber)",
                                                 turns)
                            + getTurnsInLocation("The Lower Chambers (Empty/Rubble)", turns)
                            + getTurnsInLocation("Ed the Undying", turns);

        warIslandOpeningTurns = getTurnsAfterLocationOpen("Hippy Camp", "Palindome", turns)
                                + getTurnsInLocation("Wartime Hippy Camp (Hippy Disguise)", turns)
                                + getTurnsInLocation("Wartime Hippy Camp (Frat Disguise)", turns)
                                + getTurnsAfterLocationOpen("Frat House", "Palindome", turns)
                                + getTurnsInLocation("Wartime Frat House (Hippy Disguise)", turns)
                                + getTurnsInLocation("Wartime Frat House (Frat Disguise)", turns);

        warIslandQuestTurns = getTurnsInLocation("Hatching Chamber", turns)
                              + getTurnsInLocation("Feeding Chamber", turns)
                              + getTurnsInLocation("Guards' Chamber", turns)
                              + getTurnsInLocation("Queen's Chamber", turns)
                              + getTurnsInLocation("Barrel with Something Burning in it", turns)
                              + getTurnsInLocation("Over Where the Old Tires Are", turns)
                              + getTurnsInLocation("Near an Abandoned Refrigerator", turns)
                              + getTurnsInLocation("Out by that Rusted-Out Car", turns)
                              + getTurnsInLocation("Wartime Sonofa Beach", turns)
                              + getTurnsInLocation("Themthar Hills", turns)
                              + getTurnsInLocation("Barn", turns)
                              + getTurnsInLocation("Family Plot", turns)
                              + getTurnsInLocation("Pond", turns)
                              + getTurnsInLocation("Other Back 40", turns)
                              + getTurnsInLocation("Back 40", turns)
                              + getTurnsInLocation("Granary", turns)
                              + getTurnsInLocation("Bog", turns)
                              + getTurnsInLocation("Shady Thicket", turns)
                              + getTurnsInLocation("Battlefield (Frat Uniform)", turns)
                              + getTurnsInLocation("Battlefield (Hippy Uniform)", turns)
                              + getTurnsInLocation("Big Wisniewski", turns)
                              + getTurnsInLocation("The Big Wisniewski", turns)
                              + getTurnsInLocation("The Man", turns);

        dodQuestTurns = getTurnsInLocation("Greater-Than Sign", turns)
                        + getTurnsUntilItemFound("Dungeons of Doom",
                                                 "dead mimic",
                                                 turns,
                                                 droppedItems);
    }

    /**
     * @param turns
     *            The turn rundown of the ascension.
     * @param droppedItems
     *            All dropped items during the ascension.
     * @return Turns spent.
     */
    private int calculateTempleOpening(
                                       final Collection<TurnInterval> turns,
                                       final Collection<Item> droppedItems) {
        final int spookyGro = getTurnsUntilItemFound("Spooky Forest",
                                                     "Spooky-Gro fertilizer",
                                                     turns,
                                                     droppedItems);
        final int sapling = getTurnsUntilItemFound("Spooky Forest",
                                                   "spooky sapling",
                                                   turns,
                                                   droppedItems);

        return spookyGro > sapling ? spookyGro : sapling;
    }

    /**
     * @param areaName
     *            The name of the area the turns are spent in.
     * @param turns
     *            The turn rundown of the ascension.
     * @return Turns spent.
     */
    private int getTurnsInLocation(
                                   final String areaName, final Collection<TurnInterval> turns) {
        int turnsSpent = 0;

        for (final TurnInterval ti : turns)
            if (ti.getAreaName().equals(areaName))
                turnsSpent += ti.getTotalTurns();

        return turnsSpent;
    }

    /**
     * @param areaName
     *            The name of the area the turns are spent in.
     * @param openedLocation
     *            The name of the area whose opening stops the turncounting.
     * @param turns
     *            The turn rundown of the ascension.
     * @return Turns spent.
     */
    private int getTurnsUntilOtherLocationOpen(
                                               final String areaName, final String openedLocation,
                                               final Collection<TurnInterval> turns) {
        int turnsSpent = 0;
        int firstTurnInOpenedLocation = Integer.MAX_VALUE;
        for (final TurnInterval ti : turns)
            if (ti.getAreaName().equals(openedLocation)) {
                firstTurnInOpenedLocation = ti.getStartTurn();
                break;
            }

        for (final TurnInterval ti : turns)
            if (ti.getStartTurn() >= firstTurnInOpenedLocation)
                break;
            else if (ti.getAreaName().equals(areaName))
                turnsSpent += ti.getTotalTurns();

        return turnsSpent;
    }

    /**
     * @param areaName
     *            The name of the area the turns are spent in.
     * @param alreadyOpenLocation
     *            The name of the area which has to be already open before the
     *            turncounting starts.
     * @param turns
     *            The turn rundown of the ascension.
     * @return Turns spent.
     */
    private int getTurnsAfterLocationOpen(
                                          final String areaName, final String alreadyOpenLocation,
                                          final Collection<TurnInterval> turns) {
        return getTurnsAfterLocationOpenUntilOtherLocationOpen(areaName,
                                                               alreadyOpenLocation,
                                                               "",
                                                               turns);
    }

    /**
     * @param areaName
     *            The name of the area the turns are spent in.
     * @param alreadyOpenLocation
     *            The name of the area which has to be already open before the
     *            turncounting starts.
     * @param toBeOpenedLocation
     *            The name of the area whose opening stops the turncounting.
     * @param turns
     *            The turn rundown of the ascension.
     * @return Turns spent.
     */
    private int getTurnsAfterLocationOpenUntilOtherLocationOpen(
                                                                final String areaName,
                                                                final String alreadyOpenLocation,
                                                                final String toBeOpenedLocation,
                                                                final Collection<TurnInterval> turns) {
        int turnsSpent = 0;
        int firstTurnInAlreadyOpenedLocation = Integer.MIN_VALUE;
        int firstTurnInToBeOpenedLocation = Integer.MAX_VALUE;

        for (final TurnInterval ti : turns)
            if (firstTurnInAlreadyOpenedLocation == Integer.MIN_VALUE
                && ti.getAreaName().equals(alreadyOpenLocation))
                firstTurnInAlreadyOpenedLocation = ti.getStartTurn();
            else if (ti.getAreaName().equals(toBeOpenedLocation)) {
                firstTurnInToBeOpenedLocation = ti.getStartTurn();
                break;
            }

        for (final TurnInterval ti : turns)
            if (ti.getStartTurn() >= firstTurnInAlreadyOpenedLocation)
                if (ti.getStartTurn() >= firstTurnInToBeOpenedLocation)
                    break;
                else if (ti.getAreaName().equals(areaName))
                    turnsSpent += ti.getTotalTurns();

        return turnsSpent;
    }

    /**
     * @param areaName
     *            The name of the area the turns are spent in.
     * @param itemName
     *            The name of the item which has to be found.
     * @param turns
     *            The turn rundown of the ascension.
     * @param droppedItems
     *            All dropped items during the ascension.
     * @return Turns spent.
     */
    private int getTurnsUntilItemFound(
                                       final String areaName, final String itemName,
                                       final Collection<TurnInterval> turns,
                                       final Collection<Item> droppedItems) {
        int turnsSpent = 0;
        int finishedOnTurn = Integer.MAX_VALUE;
        for (final Item i : droppedItems)
            if (i.getName().equals(itemName)) {
                finishedOnTurn = i.getFoundOnTurn();
                break;
            }

        for (final TurnInterval ti : turns)
            if (ti.getAreaName().equals(areaName))
                if (ti.getStartTurn() <= finishedOnTurn && ti.getEndTurn() <= finishedOnTurn)
                    turnsSpent += ti.getTotalTurns();
                else if (ti.getStartTurn() <= finishedOnTurn && ti.getEndTurn() > finishedOnTurn) {
                    turnsSpent += finishedOnTurn - ti.getStartTurn();
                    break;
                }

        return turnsSpent;
    }

    /**
     * @return The amount of turns the Mosquito quest took.
     */
    public int getMosquitoQuestTurns() {
        return mosquitoQuestTurns;
    }

    /**
     * @return The amount of turns opening the Hidden Temple took.
     */
    public int getTempleOpeningTurns() {
        return templeOpeningTurns;
    }

    /**
     * @return The amount of turns the Tavern quest took.
     */
    public int getTavernQuestTurns() {
        return tavernQuestTurns;
    }

    /**
     * @return The amount of turns the Bat quest took.
     */
    public int getBatQuestTurns() {
        return batQuestTurns;
    }

    /**
     * @return The amount of turns the Knob Goblin quest took.
     */
    public int getKnobQuestTurns() {
        return knobQuestTurns;
    }

    /**
     * @return The amount of turns the Friars quest took.
     */
    public int getFriarsQuestTurns() {
        return friarsQuestTurns;
    }

    /**
     * @return The amount of turns the Cyrpt quest took.
     */
    public int getCyrptQuestTurns() {
        return cyrptQuestTurns;
    }

    /**
     * @return The amount of turns the Trapzor quest took.
     */
    public int getTrapzorQuestTurns() {
        return trapzorQuestTurns;
    }

    /**
     * @return The amount of turns the Orc Chasm quest took.
     */
    public int getChasmQuestTurns() {
        return chasmQuestTurns;
    }

    /**
     * @return The amount of turns the Airship quest took.
     */
    public int getAirshipQuestTurns() {
        return airshipQuestTurns;
    }

    /**
     * @return The amount of turns the Castle quest took.
     */
    public int getCastleQuestTurns() {
        return castleQuestTurns;
    }

    /**
     * @return The amount of turns opening the ballroom took.
     */
    public int getBallroomOpeningTurns() {
        return ballroomOpeningTurns;
    }

    /**
     * @return The amount of turns the Pirate quest took.
     */
    public int getPirateQuestTurns() {
        return pirateQuestTurns;
    }

    /**
     * @return The amount of turns the Black Forest quest took.
     */
    public int getBlackForrestQuestTurns() {
        return blackForrestQuestTurns;
    }

    /**
     * @return The amount of turns finding the pyramid took.
     */
    public int getDesertOasisQuestTurns() {
        return desertOasisQuestTurns;
    }

    /**
     * @return The amount of turns the Spookyraven quest took.
     */
    public int getSpookyravenQuestTurns() {
        return spookyravenQuestTurns;
    }

    /**
     * @return The amount of turns the Hidden City quest took.
     */
    public int getTempleCityQuestTurns() {
        return templeCityQuestTurns;
    }

    /**
     * @return The amount of turns the Palindome quest took.
     */
    public int getPalindomeQuestTurns() {
        return palindomeQuestTurns;
    }

    /**
     * @return The amount of turns completing the pyramid took.
     */
    public int getPyramidQuestTurns() {
        return pyramidQuestTurns;
    }

    /**
     * @return The amount of turns opening the battlefield took.
     */
    public int getWarIslandOpeningTurns() {
        return warIslandOpeningTurns;
    }

    /**
     * @return The amount of turns completing the battlefield with all
     *         sidequests took.
     */
    public int getWarIslandQuestTurns() {
        return warIslandQuestTurns;
    }

    /**
     * @return The amount of turns the DoD quest took.
     */
    public int getDodQuestTurns() {
        return dodQuestTurns;
    }
}